var freezeLens = false;
var freezeText = false;
var hidden = false;
var x = 0;
var y = 0;

var mouseX = 0;
var mouseY = 0;
var currentElement;
var largeSize = false;

var debug = false;

var deleteNode = false;

var sampleList = 
'<li>Top Level'+
	'<ul>'+
		'<li>top-level</li>'+
		'<li>top-level</li>'+
		'<li>Mid-Level'+
			'<ul>'+
				'<li>mid-level</li>'+
				'<li>mid-level</li>'+
				'<li>Low-level'+
					'<ul>'+
						'<li>low-level</li>'+
						'<li>low-level</li>'+
					'</ul>'+
				'</li>'+
			'</ul>'+
		'</li>'+
	'</ul>'+
'</li>';

var DOMElements = new Array();
var subsections = 10;
var sectionHeight;

$(document).ready( function() {
	$('body').append(
	'<div id="lens" style="z-index:999;">'+
		'<div style="display:inline;">'+
			'<input id="exitLens" type="button" value="X" class="magic"></input>'+
			'<span id="info" class="magic" style="display:none;"></span>'+
			'<ul id="list" class="expanded magic">'+
		'</div>'+
		'</ul>'+
	'</div>');
	$('body').append(
	'<div id="debug" class="lens" style="z-index:2147483647; position:absolute; top:0; right:0; min-height:20px; width:500px; overflow:auto;">'+
		'<input id="exitDebug" type="button" style="float:left;" value="X" class="magic"></input>'+
		'<span>Debug</span></br>'+
		'<div id="debugText"></div>'+
	'</div>');
	
	if(debug){
		$('#debug').toggle(true);
	}
	else{
		$('#debug').toggle(false);
	}

	$('#exitLens').click( function() {
		hideLens();
	});
	
	$('#exitDebug').click( function() {
		debug = false;
		$('#debug').toggle("slow");
	});
	
	$(document).mousemove(function(e) {
		mouseX = e.pageX;
		mouseY = e.pageY;
	});
	
	$('div.float')
		.live('hover', 
			function(){
				if(deleteNode){
					$(this).remove();
					deleteNode = false;
				}
				$(this).css({'z-index':'2000'});
			})
		.live('mouseleave',
			function(){
				$(this).css({'z-index':'1000'});
			});
	
	
	$(document).keydown(keyPress);
	setLensPosition();
	showLens();
	createElementArrays();
	getElementsUnderLens();
});

function createElementArrays(){
	sectionHeight = $('body').height() / 10;
	DOMElements = new Array();
	for(var i = 0; i < subsections; i++){
		DOMElements.push(new Array());
	}
	loopChildren($('body'));
}

var delay = false;
var edit = false;
function keyPress(e) {
//a = 65
//s = 83
//d = 68
//w = 87
//x = 88
//shift = 16
	if(e.keyCode ==  27){ //esc
		hideLens();
	}
	if(edit)
		return;
	if(e.keyCode == 88){ //x, delete float elements
		deleteNode = true;
		return;
	}
	if(!delay) { //don't spam expensive computation
		delay = true;
		setTimeout(function() { 
			delay = false;
			deleteNode = false;
			getElementsUnderLens();
			//get elements lazily, but let the lens move
		}, 400);
	}
	var lens = $('#lens');
	$('#info').text('keydown: '+e.keyCode+'\n'+'x: '+x+' y: '+y);
	if(!hidden){
		switch(e.keyCode) {
			case 65:
				x -= 10;
				e.preventDefault();
				break;
			case 68:
				x += 10;
				e.preventDefault();
				break;
			case 83:
				y += 10;
				e.preventDefault();
				break;
			case 87:
				y -= 10;
				e.preventDefault();
				break;
		}
		if(x < 0) x = 0;
		if(y < 0) y = 0;
		setLensPosition();
		
		//keyboard shortcuts
		if(e.keyCode ==  27){ //esc
			hideLens();
			e.preventDefault();
		}
		// if(e.keyCode == 69){ //e
			// /largeSize = true;
			// setLensPosition();
			// e.preventDefault();
		// }
		// if(e.keyCode == 81){ //q
			// largeSize = false;
			// setLensPosition();
			// e.preventDefault();
		// }
		if(e.keyCode == 192){//`
			debug = !debug;
			$('#debug').toggle(debug);
			e.preventDefault();
		}
		
	}
	if(hidden && e.shiftKey && e.keyCode == 90){ //shift + z
		x = mouseX;
		y = mouseY;
		setLensPosition();
		showLens();
	}
	else if(!hidden && e.shiftKey && e.keyCode == 82){ //shift + r
		//refresh the dom tree
		//useful for if you add items via edit
		//just as slow as the initial load operation...
		$('div.float').remove();
		createElementArrays();
		getElementsUnderLens();
	}
	else if(e.shiftKey){
		x = mouseX;
		y = mouseY;
		setLensPosition();
	}
}

//lens height and width
var lensDim = 300;
function setLensPosition() {
	var width = lensDim;
	var height = lensDim;
	if(largeSize){
		width = $(document).width() - x;
	}
	$('#lens').css(
		{
			background:'rgba(0,0,0,0.1)', 
			color:'#000', 
			border:'4px solid #000', 
			position:'absolute', 
			top: y+1, 
			left :x+1,
			width: width, 
			height: height,
			overflow:'auto',
			'overflow-x': 'hidden'
		});
}

function insertFloat(jqElement, i, j){
	//var floatStart = new Date().getTime();
	var html = createChildList(jqElement);
	html = html.substring(0,400);
	//$('#debugText').append('</br>'+html);
	var div = $('<div class="float" style="z-index:1000;"></div>').append(html);
	div.css(
	{
		display: 'none',
		background: '#FFF', 
		color:'#000', 
		border:'1px solid #FFF', 
		position:'absolute', 
		//width: width,
		top: jqElement.offset().top, 
		left: jqElement.offset().left,
		//overflow:'visible',
		//'overflow-x': 'hidden'
	});
	$(div).draggable();
	$(div).dblclick( 
		function(e){
			editElement(i, j, e.pageX, e.pageY);
			$('body').append($(this));
		}
	);
	$('body').append(div);
	//var floatEnd = new Date().getTime();
	//$('#debugText').append("Float insertion: "+(floatEnd - floatStart)+'</br>');
}

function loopChildren(jqElement){
	if(jqElement.children().length > 0){
		jqElement.children().each( 
		function () {
			loopChildren($(this));
		});
	}
	//place element in DOMElements array by location.
	placeElement(jqElement);
}

function placeElement(jqElement){
	for(var i = 0; i < subsections; i++){
		if(inSection(jqElement, i))
			DOMElements[i].push(jqElement[0]);
	}
}	

function inSection(jqElement, section){
	if(!jqElement.length) return false;
	if(jqElement.css('display') == 'none') return false;
	var left = jqElement.offset().left;
	var right = jqElement.offset().left + jqElement.width();
	var top = jqElement.offset().top;
	var bottom = jqElement.offset().top + jqElement.height();
	var sectionTop = section * sectionHeight;
	var sectionBottom = sectionTop + sectionHeight;
	var width = $('body').width();
	
	var xOverlap = (left > 0 && left < width ||
                    0 > left && 0 < right);
	var yOverlap = (top > sectionTop && top < (sectionBottom) ||
					sectionTop > top && sectionBottom < bottom);
	return xOverlap && yOverlap;
}

//takes jq element as arg
function underLens(element) {
	if(!element.length) return false;
	if(element.css('display') == 'none') return false;
	var left = element.offset().left;
	var right = element.offset().left + element.width();
	var top = element.offset().top;
	var bottom = element.offset().top + element.height();
	var xOverlap = (left > x && left < (x+lensDim) ||
                    x > left && x < right);
	var yOverlap = (top > y && top < (y + lensDim) ||
					y > top && y < bottom);
	return xOverlap && yOverlap;
}

function getElementsUnderLens(){
	if(hidden)
		return;
	var lens = $('#lens');
	var elements = {};
	var element;
	hideLens();
	$('#debugText').html('');
	$('#debugText').append('lens ('+x+', '+y+')</br> ');
	$('div.float').remove();
	var loopStart = new Date().getTime();
	//loopChildren($('body'));
	var element;
	var count = 0;
	for(var i = 0; i < DOMElements.length; i++){
		//only check elements that are in this section of the screen
		if(y < (i*sectionHeight+sectionHeight) && y > i*sectionHeight ||
			y+lensDim < (i*sectionHeight+sectionHeight) && y+lensDim > i*sectionHeight ){
			for(var j = 0; j < DOMElements[i].length; j++){
				count++;
				element = $(DOMElements[i][j]);
				if(underLens(element)){
					insertFloat(element, i, j);
				}
			}
		}
	}
	//
	$('#debugText').append('There are '+count+' elements in the dom<br/>');
	var loopEnd = new Date().getTime();
	$('#debugText').append("Loop Dom : "+(loopEnd - loopStart)+'<br/>');
	$('div.float').toggle(true);
	showLens();
}

function showLens() {
	$('#lens').toggle(true);
	hidden = false;	
	if(debug){
		$('#debug').toggle(true);
	}
}
 
 function hideLens() {
	$('div.float').remove();
	$('#editDiv').remove();
	edit = false;
	$('#lens').toggle(false);
	hidden = true;
	$('#debug').toggle(false);
}
 
function createChildList(jqElement){
	var text = '';
	var html = jqElement.html();
	var tag = jqElement.get(0).nodeName.toLowerCase();
	tag = $.trim(tag);
	var attributes = ' ';
	for (var i = 0; i < jqElement[0].attributes.length; i++) {
		var attrib = jqElement[0].attributes[i];
		if(attrib.value != null){
			attributes += '<span class="id">' + attrib.name+'</span>="'+attrib.value+'" ';
		}
	}
	if(tag.length == 0) return '';
	
	return '<div class="magic">'+
				'<span class="tag">&lt;'+tag+'</span>'+
				'<span class="attr">'+attributes+'<span class="tag"> &gt;</span></span> '+
				jqElement.text()+
				'<span class="tag">&lt;/'+tag+'&gt;</span>'+
			'</div>';
}

var elementForEditing;
function insertEditForm(element, xPos, yPos){
	var jqElement = $(element);
	var div = $(
	'<div id="editDiv">'+
		'<form id="editForm"></form>'+
		'<input type="submit" value="Save" action="#"/>'+
		'<input type="submit" value="Cancel"/>'+
	'</div>');
	div.children('input[value="Save"]').click(saveEdits);
	div.children('input[value="Cancel"]').click(
		function(){
			edit = false;
			$('#editDiv').remove();
			getElementsUnderLens();
		}
	);
	var form = div.children('form');
	var innerHtml = '';
	var input = '';
	var innerHtml = unescapeHtmlEntities(element.innerHTML);
	var input = 
		'<div>'+
		'<div><b>Edit Element:</b></div>'+
		'<div>'+unescapeHtmlEntities(element.outerHTML).substring(0,50)+'...</div>'+
		'<div><b>InnerHtml:</b></div>'+
		'<textarea style="height:100px;width:400px;" aria-describedby="text">'+innerHtml+'</textarea>'+
		'</div>';
	form.append(input);
	var editElements = false; //only put the 'edit attributes' line in if they exist
	for (var i = 0; i < element.attributes.length; i++) {
		var attrib = element.attributes[i];
		if(attrib.value != null){
			if(!editElements){
				editElements = true;
				form.append('<div><b>Edit Attributes:</b></div>');
			}
			form.append(
				'<div>'+
					attrib.name+': '+
					'<input aria-describedby="'+attrib.name+'" type="text" value="'+attrib.value+'">'+
				'</div>');
			
		}
	}
	if(form.children().length < 1){
		form.remove();
		return;
	}
	form.append('<div><b>Add new attributes:</b></div>');
	//remove all of the floats, but we'll replace the one being edited
	$('div.float').remove();
	div.css(
	{
		background: '#FFF', 
		color:'#000', 
		position:'absolute', 
		top: yPos + 5, 
		left: xPos,
		overflow:'visible',
		'z-index': '2000'
	});
	$(div).draggable();
	$('body').append(div);
	insertCustomField();
	elementForEditing = jqElement;
	edit = true;
}

function insertCustomField(){
	var attributeInput = $('<input aria-describedby="attribute" type="text" value="">');
	var customField = $(
		'<div style="display:inline;">'+
			'<div class="attributeDiv" style="display:block;">'+
				'<span>Attribute</span>'+
			'</div>'+
			'<div style="display:block;">'+
				'<span>Value</span>'+
				'<input aria-describedby="custom" type="text" value="">'+
			'</div>'+
		'</div>');
	attributeInput.click(function() {
		if($(this).val().length < 1)
			insertCustomField();
	});
	customField.children('div.attributeDiv').append(attributeInput);
	$('#editForm').append(customField);
}

function editElement(i, j, xPos, yPos){
	var element = DOMElements[i][j];
	$('#editDiv').remove();
	insertEditForm(element, xPos, yPos);
}

function saveEdits() {
	var inputs = $('#editForm > div > input');
	var input;
	var text = $('#editForm > div > textarea');
	var textValue = text.val();
	elementForEditing.html(textValue);
	//regular attr/value pairs
	for(var i = 0; i < inputs.length; i++){
		input = $(inputs[i]);
		var attr = input.attr('aria-describedby');
		if(attr == null || attr.trim() == '')
			continue;
		var value = input.val();
		elementForEditing.attr(attr, value);
	}
	
	//custom edits are two divs deep
	inputs = $('#editForm > div > div > input');
	for(var i = 0; i < inputs.length; i++){
		input = $(inputs[i]);
		var attr = input.attr('aria-describedby');
		//custom edit of attribute
		if(attr == 'attribute'){
			attr = input.val();
			//get attribute name from this input, increment i to get value from next input
			i++;
			input = $(inputs[i]);
		}
		if(attr == null || attr.trim() == '')
			continue;
		var value = input.val();
		elementForEditing.attr(attr, value);
	}
	
	getElementsUnderLens();
	$('#editDiv').remove();
	edit = false;
}

var escapeHtmlEntities = (function() {
  var translate_re = /&(nbsp|amp|quot|lt|gt);/g;
  var translate = {
    "nbsp": " ", 
    "amp" : "&", 
    "quot": "\"",
    "lt"  : "<", 
    "gt"  : ">"
  };
  return function(s) {
    return ( s.replace(translate_re, function(match, entity) {
	alert("s: "+s+" translated: "+ranslate[entity]);
      return translate[entity]; 
    }) );
  }
})();

function unescapeHtmlEntities(s){
	var translate_re = /[&<>"]/g;
	var translate = {
		"&": "&amp;",
		"<": "&lt;",
		">": "&gt;",
		'"': "&quot;",
		" ": "&nbsp;"
	};
	return ( s.replace(translate_re, 
		function(match) { 
			return translate[match]; 
		})
	);
}
